﻿#include "SpellObject.h"
#include "../MirEnvir/Envir.h"
#include "../../Shared/Globals.h"
#include "../../Shared/Functions/Functions.h"
#include "../MirEnvir/Map.h"
#include "../../Shared/ServerPackets.h"
#include "../../Shared/Data/Stat.h"

using namespace Server::MirEnvir;
namespace S = ServerPackets;

namespace Server::MirObjects
{

    ObjectType SpellObject::getRace() const
    {
        return ObjectType::Spell;
    }

    std::string SpellObject::getName() const
    {
        return Name;
    }

    void SpellObject::setName(const std::string &value)
    {
        Name = value;
    }

    int SpellObject::getCurrentMapIndex() const
    {
        return CurrentMapIndex;
    }

    void SpellObject::setCurrentMapIndex(int value)
    {
        CurrentMapIndex = value;
    }

    Point *SpellObject::getCurrentLocation() const
    {
        return CurrentLocation;
    }

    void SpellObject::setCurrentLocation(Point *value)
    {
        CurrentLocation = value;
    }

    MirDirection SpellObject::getDirection() const
    {
        return Direction;
    }

    void SpellObject::setDirection(MirDirection value)
    {
        Direction = value;
    }

    unsigned short SpellObject::getLevel() const
    {
        return Level;
    }

    void SpellObject::setLevel(unsigned short value)
    {
        Level = value;
    }

    bool SpellObject::getBlocking() const
    {
        return false;
    }

    int SpellObject::getHealth() const
    {
        throw NotSupportedException();
    }

    int SpellObject::getMaxHealth() const
    {
        throw NotSupportedException();
    }

    void SpellObject::Process()
    {
        if (Decoration)
        {
            return;
        }

        if (Caster != nullptr && Caster->Node == nullptr)
        {
            Caster = nullptr;
        }

        if (getEnvir()->getTime() > ExpireTime || ((Spell == Spell::FireWall || Spell == Spell::Portal || Spell == Spell::ExplosiveTrap || Spell == Spell::Reincarnation) && Caster == nullptr) || (Spell == Spell::TrapHexagon && getTarget() != nullptr) || (Spell == Spell::Trap && getTarget() != nullptr))
        {
            if (Spell == Spell::TrapHexagon && getTarget() != nullptr || Spell == Spell::Trap && getTarget() != nullptr)
            {
                MonsterObject *ob = static_cast<MonsterObject*>(getTarget());

                if (getEnvir()->getTime() < ExpireTime && ob->ShockTime != 0)
                {
                    return;
                }
            }

            if (Spell == Spell::Reincarnation && Caster != nullptr)
            {
                Caster->ReincarnationReady = true;
                Caster->ReincarnationExpireTime = getEnvir()->getTime() + 6000;
            }

            getCurrentMap()->RemoveObject(this);
            Despawn();
            return;
        }

        if (Spell == Spell::FireWall)
        {
            if (getCurrentMap() != Caster == nullptr ? nullptr : Caster->getCurrentMap())
            {
                getCurrentMap()->RemoveObject(this);
                Despawn();
                return;
            }
        }

        if (Spell == Spell::Reincarnation && !Caster->ActiveReincarnation)
        {
            getCurrentMap()->RemoveObject(this);
            Despawn();
            return;
        }

        if (Spell == Spell::ExplosiveTrap && Caster != nullptr && !Functions::InRange(getCurrentLocation(), Caster->getCurrentLocation(), 15))
        {
            getCurrentMap()->RemoveObject(this);
            Despawn();
            return;
        }

        if (getEnvir()->getTime() < TickTime)
        {
            return;
        }
        TickTime = getEnvir()->getTime() + TickSpeed;

        Cell *cell = getCurrentMap()->GetCell(getCurrentLocation());
        for (int i = 0; i < cell->Objects.size(); i++)
        {
            ProcessSpell(cell->Objects[i]);
        }

        if ((Spell == Spell::MapLava) || (Spell == Spell::MapLightning))
        {
            Value = 0;
        }
    }

    void SpellObject::ProcessSpell(MapObject *ob)
    {
        if (getEnvir()->getTime() < StartTime)
        {
            return;
        }
        switch (Spell)
        {
            case Spell::FireWall:
            {
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }

                    if (!ob->IsAttackTarget(Caster))
                    {
                        return;
                    }
                    ob->Attacked(Caster, Value, DefenceType::MAC, false);
            }
                break;
            case Spell::Healing: //SafeZone
            {
                    if (ob->getRace() != ObjectType::Player && (ob->getRace() != ObjectType::Monster || ob->Master == nullptr || ob->Master->getRace() != ObjectType::Player))
                    {
                        return;
                    }
                    if (ob->Dead || ob->HealAmount != 0 || ob->getPercentHealth() == 100)
                    {
                        return;
                    }

                    ob->HealAmount += 25;
                    S::ObjectEffect *tempVar = new S::ObjectEffect();
                    tempVar->ObjectID = ob->ObjectID;
                    tempVar->Effect = SpellEffect::Healing;
                    Broadcast(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            }
                break;
            case Spell::PoisonCloud:
            {
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }

                    if (!ob->IsAttackTarget(Caster))
                    {
                        return;
                    }
                    ob->Attacked(Caster, Value, DefenceType::MAC, false);
                    if (!ob->Dead)
                    {
                        Poison *tempVar2 = new Poison(), *Caster, *false, *false;
                        tempVar2->Duration = 15;
                        tempVar2->Owner = Caster;
                        tempVar2->PType = PoisonType::Green;
                        tempVar2->TickSpeed = 2000;
                        tempVar2->Value = Value / 20;
                        ob->ApplyPoison(tempVar2, Caster, false, false);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was passed to a method or constructor. Handle memory management manually.
                    }
            }
                break;
            case Spell::Blizzard:
            {
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    if (Caster != nullptr && Caster->ActiveBlizzard == false)
                    {
                        return;
                    }
                    if (!ob->IsAttackTarget(Caster))
                    {
                        return;
                    }
                    ob->Attacked(Caster, Value, DefenceType::MAC, false);
                    if (!ob->Dead && getEnvir()->Random->Next(8) == 0)
                    {
                        Poison *tempVar3 = new Poison(), *Caster;
                        tempVar3->Duration = 5 + getEnvir()->Random->Next(Caster->Stats[Stat::Freezing]);
                        tempVar3->Owner = Caster;
                        tempVar3->PType = PoisonType::Slow;
                        tempVar3->TickSpeed = 2000;
                        ob->ApplyPoison(tempVar3, Caster);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar3' statement was not added since tempVar3 was passed to a method or constructor. Handle memory management manually.
                    }
            }
                break;
            case Spell::MeteorStrike:
            {
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    if (Caster != nullptr && Caster->ActiveBlizzard == false)
                    {
                        return;
                    }
                    if (!ob->IsAttackTarget(Caster))
                    {
                        return;
                    }
                    ob->Attacked(Caster, Value, DefenceType::MAC, false);
            }
                break;
            case Spell::ExplosiveTrap:
            {
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    if (!ob->IsAttackTarget(Caster))
                    {
                        return;
                    }
                    if (DetonatedTrap)
                    {
                        return;
                    }
                    DetonateTrapNow();
                    ob->Attacked(Caster, Value, DefenceType::MAC, false);
            }
                break;
            case Spell::MapLava:
            case Spell::MapLightning:
            {
                    PlayerObject *player = dynamic_cast<PlayerObject*>(ob);
                    if (player != nullptr)
                    {
                        if (player->Account->AdminAccount && player->getObserver())
                        {
                            return;
                        }
                        player->Struck(Value, DefenceType::MAC);
                    }
            }
                break;
            case Spell::MapQuake1:
            case Spell::MapQuake2:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::MAC);
            }
                break;
            case Spell::GeneralMeowMeowThunder:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::MAC);
            }
                break;
            case Spell::TreeQueenRoot:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::MAC);
            }
                break;
            case Spell::TreeQueenMassRoots:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::MAC);
            }
                break;
            case Spell::TreeQueenGroundRoots:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::MAC);

                    if (getEnvir()->Random->Next(3) > 0)
                    {
                        Poison *tempVar4 = new Poison(), *this;
                        tempVar4->PType = PoisonType::Paralysis;
                        tempVar4->Duration = 5;
                        tempVar4->TickSpeed = 1000;
                        ob->ApplyPoison(tempVar4, this);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar4' statement was not added since tempVar4 was passed to a method or constructor. Handle memory management manually.
                    }
            }
                break;
            case Spell::StoneGolemQuake:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::AC);
            }
                break;
            case Spell::EarthGolemPile:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::AC);
            }
                break;
            case Spell::TucsonGeneralRock:
            {
                    if (Value == 0)
                    {
                        return;
                    }
                    if (ob->getRace() != ObjectType::Player && ob->getRace() != ObjectType::Monster)
                    {
                        return;
                    }
                    if (ob->Dead)
                    {
                        return;
                    }
                    ob->Struck(Value, DefenceType::AC);
            }
                break;
            case Spell::Portal:
            {
                    if (ob->getRace() != ObjectType::Player)
                    {
                        return;
                    }
                    if (Caster != ob && (Caster == nullptr || (Caster->GroupMembers.empty()) || (!std::find(Caster->GroupMembers.begin(), Caster->GroupMembers.end(), static_cast<PlayerObject*>(ob) != Caster->GroupMembers.end()))))
                    {
                        return;
                    }

                    auto portal = getEnvir()->Spells.SingleOrDefault([&] (std::any ob)
                    {
                        return ob != this && ob->Node != nullptr && ob->Spell == Spell::Portal && ob->Caster == Caster;
                    });

                    if (portal != nullptr)
                    {
                        Point *newExit = Functions::PointMove(portal->CurrentLocation, ob->getDirection(), 1);

                        if (!portal->CurrentMap.ValidPoint(newExit))
                        {
                            return;
                        }

                        ob->Teleport(portal->CurrentMap, newExit, false);
                    }

                    Value -= 1;

                    if (Value < 1)
                    {
                        ExpireTime = 0;
                    }
            }
                break;
        }
    }

    void SpellObject::DetonateTrapNow()
    {
        DetonatedTrap = true;
        Broadcast(GetInfo());
        ExpireTime = getEnvir()->getTime() + 1000;
    }

    void SpellObject::SetOperateTime()
    {
        long long time = getEnvir()->getTime() + 2000;

        if (TickTime < time && TickTime > getEnvir()->getTime())
        {
            time = TickTime;
        }

        if (OwnerTime < time && OwnerTime > getEnvir()->getTime())
        {
            time = OwnerTime;
        }

        if (ExpireTime < time && ExpireTime > getEnvir()->getTime())
        {
            time = ExpireTime;
        }

        if (PKPointTime < time && PKPointTime > getEnvir()->getTime())
        {
            time = PKPointTime;
        }

        if (LastHitTime < time && LastHitTime > getEnvir()->getTime())
        {
            time = LastHitTime;
        }

        if (EXPOwnerTime < time && EXPOwnerTime > getEnvir()->getTime())
        {
            time = EXPOwnerTime;
        }

        if (BrownTime < time && BrownTime > getEnvir()->getTime())
        {
            time = BrownTime;
        }

        for (int i = 0; i < ActionList.size(); i++)
        {
            if (ActionList[i]->Time >= time && ActionList[i]->Time > getEnvir()->getTime())
            {
                continue;
            }
            time = ActionList[i]->Time;
        }

        for (int i = 0; i < PoisonList.size(); i++)
        {
            if (PoisonList[i]->TickTime >= time && PoisonList[i]->TickTime > getEnvir()->getTime())
            {
                continue;
            }
            time = PoisonList[i]->TickTime;
        }

        for (int i = 0; i < Buffs.size(); i++)
        {
            if (Buffs[i]->ExpireTime >= time && Buffs[i]->ExpireTime > getEnvir()->getTime())
            {
                continue;
            }
            time = Buffs[i]->ExpireTime;
        }


        if (OperateTime <= getEnvir()->getTime() || time < OperateTime)
        {
            OperateTime = time;
        }
    }

    void SpellObject::Process(DelayedAction *action)
    {
        throw NotSupportedException();
    }

    bool SpellObject::IsAttackTarget(PlayerObject *attacker)
    {
        throw NotSupportedException();
    }

    bool SpellObject::IsAttackTarget(MonsterObject *attacker)
    {
        throw NotSupportedException();
    }

    int SpellObject::Attacked(PlayerObject *attacker, int damage, DefenceType type, bool damageWeapon)
    {
        throw NotSupportedException();
    }

    int SpellObject::Attacked(MonsterObject *attacker, int damage, DefenceType type)
    {
        throw NotSupportedException();
    }

    int SpellObject::Struck(int damage, DefenceType type)
    {
        throw NotSupportedException();
    }

    bool SpellObject::IsFriendlyTarget(PlayerObject *ally)
    {
        throw NotSupportedException();
    }

    bool SpellObject::IsFriendlyTarget(MonsterObject *ally)
    {
        throw NotSupportedException();
    }

    void SpellObject::ReceiveChat(const std::string &text, ChatType type)
    {
        throw NotSupportedException();
    }

    Packet *SpellObject::GetInfo()
    {
        switch (Spell)
        {
            case Spell::Healing:
                return nullptr;
            case Spell::PoisonCloud:
            case Spell::Blizzard:
            case Spell::MeteorStrike:
            case Spell::StoneGolemQuake:
            case Spell::EarthGolemPile:
            case Spell::TreeQueenMassRoots:
            case Spell::TreeQueenGroundRoots:
                if (!Show)
                {
                    return nullptr;
                }

                S::ObjectSpell *tempVar = new S::ObjectSpell();
                tempVar->ObjectID = ObjectID;
                tempVar->Location = CastLocation;
                tempVar->Spell = Spell;
                tempVar->Direction = getDirection();

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was used in a 'return' or 'throw' statement.
                return tempVar;
            case Spell::ExplosiveTrap:
                S::ObjectSpell *tempVar2 = new S::ObjectSpell();
                tempVar2->ObjectID = ObjectID;
                tempVar2->Location = getCurrentLocation();
                tempVar2->Spell = Spell;
                tempVar2->Direction = getDirection();
                tempVar2->Param = DetonatedTrap;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was used in a 'return' or 'throw' statement.
                return tempVar2;
            default:
                S::ObjectSpell *tempVar3 = new S::ObjectSpell();
                tempVar3->ObjectID = ObjectID;
                tempVar3->Location = getCurrentLocation();
                tempVar3->Spell = Spell;
                tempVar3->Direction = getDirection();

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar3' statement was not added since tempVar3 was used in a 'return' or 'throw' statement.
                return tempVar3;
        }

    }

    void SpellObject::ApplyPoison(Poison *p, MapObject *Caster, bool NoResist, bool ignoreDefence)
    {
        throw NotSupportedException();
    }

    void SpellObject::Die()
    {
        throw NotSupportedException();
    }

    int SpellObject::Pushed(MapObject *pusher, MirDirection dir, int distance)
    {
        throw NotSupportedException();
    }

    void SpellObject::SendHealth(PlayerObject *player)
    {
        throw NotSupportedException();
    }

    void SpellObject::Spawned()
    {
        MapObject::Spawned();

        getEnvir()->Spells.push_back(this);
    }

    void SpellObject::Despawn()
    {
        MapObject::Despawn();

        getEnvir()->Spells.Remove(this);

        if (Spell == Spell::Reincarnation && Caster != nullptr && Caster->Node != nullptr)
        {
            Caster->ActiveReincarnation = false;
            Caster->Enqueue(new S::CancelReincarnation { });
        }

        if (Spell == Spell::ExplosiveTrap && Caster != nullptr)
        {
            auto linkedTraps = getCurrentMap()->GetSpellObjects(Spell::ExplosiveTrap, Owner).Where([&] (std::any x)
            {
                return x->ExplosiveTrapID == ExplosiveTrapID;
            });

            for (auto trap : *linkedTraps)
            {
                trap->DetonateTrapNow();
            }
        }

        if (Spell == Spell::Portal && Caster != nullptr)
        {
            auto portal = getEnvir()->Spells.SingleOrDefault([&] (std::any ob)
            {
                return ob::Node != nullptr && ob != this && ob->Spell == Spell::Portal && ob->Caster == Caster;
            });

            if (portal != nullptr)
            {
                portal->ExpireTime = 0;
                portal->Process();
            }
        }
    }

    void SpellObject::BroadcastInfo()
    {
        if ((Spell != Spell::ExplosiveTrap) || (Caster == nullptr))
        {
            MapObject::BroadcastInfo();
        }

        Packet *p;
        if (getCurrentMap() == nullptr)
        {
            return;
        }

        for (int i = getCurrentMap()->Players.size() - 1; i >= 0; i--)
        {
            PlayerObject *player = getCurrentMap()->Players[i];
            if (Functions::InRange(getCurrentLocation(), player->getCurrentLocation(), Globals::DataRange))
            {
                if ((Caster == nullptr) || (player == nullptr))
                {
                    continue;
                }
                if ((player == Caster) || (player->IsFriendlyTarget(Caster)))
                {
                    p = GetInfo();
                    if (p != nullptr)
                    {
                        player->Enqueue(p);
                    }
                }
            }
        }
    }
}
